package com.googlecode.yoohoo.host;

import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.SynchronousBundleListener;

import com.googlecode.yoohoo.osgi.IBundleContextAware;
import com.googlecode.yoohoo.server.IServer;
import com.googlecode.yoohoo.utils.OsgiUtils;

public class Host implements SynchronousBundleListener {
	private static final String KEY_YOOHOO_SERVER_CONTRIBUTION = "YooHoo-Server";
	
	private static final Log log = LogFactory.getLog(Host.class);
	private Map<String, IServer> servers;
	
	public Host() {
		servers = new HashMap<String, IServer>();
	}

	public void start(BundleContext bundleContext) {
		bundleContext.addBundleListener(this);
		
		Bundle[] installedBundles = bundleContext.getBundles();
		for (Bundle installed : installedBundles) {
			if (installed.getState() == Bundle.ACTIVE) {
				try {
					seekAndStartServer(installed.getBundleContext());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private IServer seekAndStartServer(BundleContext bundleContext) throws Exception {
		Dictionary<String, String> headers = bundleContext.getBundle().getHeaders();
		if (headers == null)
			return null;
		
		String serverClassString = headers.get(KEY_YOOHOO_SERVER_CONTRIBUTION);
		if (serverClassString == null)
			return null;
		
		try {
			Class<?> serverClass = bundleContext.getBundle().loadClass(serverClassString);
			if (!IServer.class.isAssignableFrom(serverClass)) {
				log.error(String.format("Server class '%s' from bundle '%s' should implement %s interface.",
						serverClassString, OsgiUtils.getSymbolicName(bundleContext.getBundle()), IServer.class.getName()));
				
				return null;
			}
			
			IServer server = (IServer)serverClass.newInstance();
			if (server instanceof IBundleContextAware) {
				((IBundleContextAware)server).setBundleContext(bundleContext);
			}
			
			server.start();
			servers.put(bundleContext.getBundle().getBundleId() + "_" + serverClassString, server);
		} catch (ClassNotFoundException e) {
			log.warn(String.format("Server class '%s' from bundle '%s' not found.",
					serverClassString, OsgiUtils.getSymbolicName(bundleContext.getBundle())));
			throw e;
		} catch (InstantiationException e) {
			log.warn(String.format("Can't instantiate server class '%s' from bundle '%s'.",
					serverClassString, OsgiUtils.getSymbolicName(bundleContext.getBundle())));
			throw e;
		} catch (IllegalAccessException e) {
			log.warn(String.format("Can't instantiate server class '%s' from bundle '%s'.",
					serverClassString, OsgiUtils.getSymbolicName(bundleContext.getBundle())));
			throw e;
		} catch (Exception e) {
			log.warn(String.format("Can't start server '%s' from bundle '%s'.",
					serverClassString, OsgiUtils.getSymbolicName(bundleContext.getBundle())));
			throw e;
		}
		
		return null;
	}

	public void stop(BundleContext bundleContext) {
		for (IServer server : servers.values()) {
			try {
				server.stop();
			} catch (Exception e) {
				log.warn(String.format("An error occured while stopping bundle %s's server contribution.",
						OsgiUtils.getSymbolicName(bundleContext.getBundle())), e);
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void bundleChanged(BundleEvent event) {
		if (event.getType() == BundleEvent.STARTED) {
			try {
				seekAndStartServer(event.getBundle().getBundleContext());
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else if (event.getType() == BundleEvent.STOPPED) {
			Dictionary<String, String> headers = event.getBundle().getHeaders();
			if (headers == null)
				return;
				
			String serverClassString = headers.get(KEY_YOOHOO_SERVER_CONTRIBUTION);
			if (serverClassString == null)
				return;
				
			IServer server = servers.remove(event.getBundle().getBundleId() + "_" + serverClassString);
			if (server != null) {
				try {
					server.stop();
				} catch (Exception e) {
					log.warn(String.format("An error occured while stopping server '%s' from bundle '%s'.",
							serverClassString, OsgiUtils.getSymbolicName(event.getBundle())), e);
					e.printStackTrace();
				}				
			}
		}			
	}

}
